<?php

namespace chick1993\util\sheet;

use chick1993\util\libs\base\SheetBase;
use chick1993\util\Utils;
use Vtiful\Kernel\Excel;

/**
 *  ```
 *  示例:
 * 1.保存数据到文件
 *  Write::file('xxx/xxx.xlsx',‘sheet1‘)->header($header)->data($data)->save();
 * 2.导出数据到浏览器
 *  Write::file('xxx/xxx.xlsx')->header($header)->data($data)->output();
 * 3.下载导入时产生的异常数据
 *  Write::file('xxx/xxx.xlsx')->errors($read)->output();
 *
 * ```
 */
class Write extends SheetBase
{
    protected $data           = [];
    protected $header         = [];
    protected $relation       = [];
    protected $allowSetHeader = true;
    protected $allowSetData   = true;
    protected $formats        = [];

    /**
     * @var array 需要设置样式的单元格['A1'=>[data=>'xxx',key=>'bgc_xx']]
     */
    protected $richText = [];

    protected function __construct(string $filename, string $sheetName = 'Sheet1', bool $mkDir = true)
    {
        $this->_formatFileInfo($filename);
        if ($mkDir && !is_dir($this->path)) {
            mkdir($this->path, 0755, true);
        }

        if (empty($sheetName)) $sheetName = 'Sheet1';

        $excel = new Excel(['path' => $this->path]);
        $this->excel = $excel->fileName($this->name, $sheetName);
    }

    /**
     * @param string $filename 生成的文件
     * @param string $sheetName 工作表名
     * @param bool $mkdir 是否自动创建文件夹
     * @return self
     */
    static public function file(string $filename, string $sheetName = 'Sheet1', bool $mkdir = true): self
    {
        return new self($filename, $sheetName, $mkdir);
    }

    /**
     * 添加样式资源
     * @param string $key
     * @param resource $format
     * @return self
     */
    public function addFormat(string $key, $format): self
    {
        $this->formats[$key] = $format;
        return $this;
    }

    /**
     * 设置富文本单元格
     * @param string $cell 单元格
     * @param mixed $data 写入单元格的数据
     * @param string $key 通过addFormat添加的Key，key不存在不会添加该内容
     * @return $this
     */
    public function addRichText(string $cell, string $key, $data = ''): self
    {
        if (!empty($this->formats[$key])) {
            $this->richText[$cell] = [
                'data' => $data,
                'key'  => $key
            ];
        }
        return $this;
    }

    /**
     * 设置导出数据和表头字段的关系
     * @param array $relation ['name'=>'名称','create_time'=>'创建时间',...] data里的name字段自动对应到名称列
     * @param mixed $default 为空时的默认值
     * @return $this
     */
    public function relation(array $relation, $default = null): self
    {
        $this->allowSetHeader = false;
        $this->relation = $relation;
        $this->header = array_values($relation);
        return $this;
    }

    /**
     * 设置导出的表头
     * @param string[] $header ['a0','b0','c0', ...]
     * @return $this
     */
    public function header(array $header): self
    {
        if ($this->allowSetHeader && $this->allowSetData) {
            $this->header = $header;
        }
        return $this;
    }

    /**
     * 设置导出的数据
     * @param string[][] $data [['a1','b1', ...],['a2','b2', ...], ...]
     * @return $this
     */
    public function data(array $data): self
    {
        if ($this->allowSetData) {
            $this->data = $data;
        }
        return $this;
    }

    /**
     * 将读取时的异常生成文件
     * @param Read          $read       读取的对象
     * @param bool          $onlyErrRow 是否仅导出错误行
     * @param int           $headerRow  标题行
     * @param callable|null $func
     * @return self
     */
    public function errors(Read $read, bool $onlyErrRow = true, int $headerRow = 0, callable $func = null): self
    {
        $this->allowSetData = false;
        $this->richText = [];
        $errors = $read->_getRowExceptions();
        $header = array_values($read->getRow($headerRow));
        $header[] = '错误信息';

        $call     = is_callable($func) ? function ($d) use ($func): array {
            $res = $func($d);
            return is_array($res) ? $res : $d;
        } : function ($d): array {
            return $d;
        };

        $data = [];
        $errRow = 0;
        $start = $headerRow + 1;
        $itr = $read->_getRowIterator($start);
        foreach ($itr as $row => $item) {
            $index = $row + $start;
            $error = $errors[$index] ?? [];
            $msg = '';
            $item = $call($item);
            foreach ($error as $e) {
                $cell = Utils::cellFromIndex($e['col'], $e['row']);
                $errCell = Utils::cellFromIndex($e['col'], $errRow);
                $msg .= "$cell:{$e['msg']};";

                $hasFormat = false;
                $format = new \Vtiful\Kernel\Format($this->excel->getHandle());
                $cKey = "color_{$e['color']}_{$e['bgColor']}";
                if (isset($e['color'])) {
                    $hasFormat = true;
                    $format = $format->fontColor($e['color']);
                }
                if (isset($e['bgColor'])) {
                    $hasFormat = true;
                    $format = $format->background($e['bgColor']);
                }

                if ($hasFormat) {
                    empty($this->formats[$cKey]) && $this->addFormat($cKey, $format->toResource());
                    $this->addRichText($errCell, $cKey, $item[$e['col']] ?? '');
                }

            }

            $item[] = $msg;
            if (!$onlyErrRow) {//
                $data[] = $item;
                $errRow += 1;
            } else if (!empty($error)) {
                $data[] = $item;
                $errRow += 1;
            }
        }
        $this->header = $header;
        $this->data = $data;
        return $this;
    }

    protected function saveToFile(): string
    {
        $data = [];
        if (!empty($this->relation)) {
            foreach ($this->data as $k => $d) {
                $temp = [];
                foreach ($this->relation as $r => $v) {
                    $temp[] = $d[$r];
                }
                $data[] = $temp;
            }
        } else {
            $data = $this->data;
        }

        $file = $this->excel->header($this->header)->data($data);
        if (!empty($this->richText)) {
            foreach ($this->richText as $cell => $t) {
                if (!empty($this->formats[$t['key']])) {
                    $i = Utils::cellToIndex($cell);
                    $file = $file->insertText(
                        $i['row'], $i['col'], $t['data'], null,
                        $this->formats[$t['key']]
                    );
                }
            }
        }
        return $file->output();
    }

    /**
     * 保存到file方法传入的文件地址
     * @return string
     */
    public function save(): string
    {
        return $this->saveToFile();
    }


    /**
     * 将文件输出到浏览器
     * @param string $downName 下载时的文件名
     * @param bool $unlink
     * @return string
     */
    public function output(string $downName = '', bool $unlink = true): string
    {
        $dest = 'php://output';
        $file = $this->saveToFile();
        $size = filesize($file);
        $mime = mime_content_type($file);

        if (!empty($downName)) {
            $downName .= ".{$this->ext}";
        } else {
            $downName = $this->name;
        }
        $downName = urlencode($downName);

        header("Content-Type: $mime");
        header("Content-Disposition: attachment;filename=$downName");
        header("Content-Length: $size");
        header('Content-Transfer-Encoding: binary');
        ob_clean();
        flush();
        copy($file, $dest);
        ob_end_flush();
        flush();
        if ($unlink) @unlink($file);
        return $file;
    }
}